import * as React from 'react';
import * as PropTypes from 'prop-types';
import classNames from 'classnames';
import Spin from '@/components/Spin';
import Pagination from '@/components/Pagination';
import createStore from 'antd/es/table/createStore'
import {
  Row
} from 'antd/es/grid';

import Item from 'antd/es/list/Item';
import { flatArray } from 'antd/es/table/util'

function getRowSelection(props) {
  return props.rowSelection || {};
}

export default class List extends React.Component {
  static Item = Item;
  static childContextTypes = {
    grid: PropTypes.any,
    itemLayout: PropTypes.string,
  };
  static defaultProps = {
    rowKey: 'id',
    data: [],
    bordered: false,
    split: true,
    loading: false,
    pagination: false,
    emptyText: '暂无数据'
  };
  defaultPaginationProps = {
    pageNumber: 1,
    total: 0,
  };
  keys = {};
  onPaginationChange = this.triggerPaginationEvent('onChange');
  onPaginationShowSizeChange = this.triggerPaginationEvent('onShowSizeChange');
  constructor(props) {
    super(props);

    const {
      pagination
    } = props;
    const paginationObj = pagination && typeof pagination === 'object' ? pagination : {};

    this.state = {
      paginationCurrent: paginationObj.pageNumber || 1,
      paginationSize: paginationObj.pageSize || 10,
    };

    this.CheckboxPropsCache = {};

    this.store = createStore({
      selectedRowKeys: getRowSelection(props).selectedRowKeys || [],
      selectionDirty: false,
    });
  }
  getCheckboxPropsByItem = (item, index) => {
    const rowSelection = getRowSelection(this.props);
    if (!rowSelection.getCheckboxProps) {
      return {};
    }
    const key = this.getRecordKey(item, index);
    if (!this.CheckboxPropsCache[key]) {
      this.CheckboxPropsCache[key] = rowSelection.getCheckboxProps(item)
    }
    return this.CheckboxPropsCache[key];
  };
  getRecordKey = (data, index) => {
    const {
      rowKey
    } = this.props;
    const dataKey =
      typeof rowKey === 'function' ? rowKey(data, index) : data[rowKey];

    return dataKey === undefined ? index : dataKey;
  };
  getDefaultSelection() {
    const rowSelection = getRowSelection(this.props);
    if (!rowSelection.getCheckboxProps) {
      return [];
    }
    const data = this.getCurrentPageData()
    return data
      .filter((item, index) => this.getCheckboxPropsByItem(item, index).defaultChecked)
      .map((record, index) => this.getRecordKey(record, index));
  }
  componentWillReceiveProps(nextProps) {
    if (nextProps.rowSelection && 'selectedRowKeys' in nextProps.rowSelection) {
      this.store.setState({
        selectedRowKeys: nextProps.rowSelection.selectedRowKeys || [],
      });
    } else if (this.props.rowSelection && !nextProps.rowSelection) {
      this.store.setState({
        selectedRowKeys: [],
      });
    }
    if ('data' in nextProps && nextProps.data !== this.props.data) {
      this.store.setState({
        selectionDirty: false,
      });
    }

    this.CheckboxPropsCache = {};
  }
  getLocalData() {
    const { data } = this.props
    let _data = data || []
    _data = _data.slice(0)
    return _data
  }
  hasPagination(props) {
    return (props || this.props).pagination !== false;
  }
  getCurrentPageData() {
    let data = this.getLocalData();
    let pageNumber;
    let pageSize;
    const state = this.state;
    if (!this.hasPagination()) {
      pageSize = Number.MAX_VALUE;
      pageNumber = 1;
    } else {
      pageSize = state.paginationSize;
      pageNumber = this.getMaxCurrent(data.length);
    }

    if (data.length > pageSize || pageSize === Number.MAX_VALUE) {
      data = data.filter((_, i) => {
        return i >= (pageNumber - 1) * pageSize && i < pageNumber * pageSize;
      });
    }
    return data
  }
  getMaxCurrent(total) {
    const {
      paginationCurrent,
      paginationSize
    } = this.state;
    if ((paginationCurrent - 1) * paginationSize >= total) {
      return Math.floor((total - 1) / paginationSize) + 1;
    }
    return paginationCurrent;
  }
  getFlatData() {
    const {
      childrenColumnName
    } = this.props;
    return flatArray(this.getLocalData(null, false), childrenColumnName);
  }
  getFlatCurrentPageData() {
    const {
      childrenColumnName
    } = this.props;
    return flatArray(this.getCurrentPageData(), childrenColumnName);
  }
  setSelectedRowKeys(selectedRowKeys, selectionInfo) {
    const {
      selectWay,
      record,
      checked,
      changeRowKeys,
      nativeEvent
    } = selectionInfo;
    const rowSelection = getRowSelection(this.props);
    if (rowSelection && !('selectedRowKeys' in rowSelection)) {
      this.store.setState({
        selectedRowKeys
      });
    }
    const data = this.getFlatData();
    if (!rowSelection.onChange && !rowSelection[selectWay]) {
      return;
    }
    const selectedRows = data.filter(
      (row, i) => selectedRowKeys.indexOf(this.getRecordKey(row, i)) >= 0,
    );
    if (rowSelection.onChange) {
      rowSelection.onChange(selectedRowKeys, selectedRows);
    }
    if (selectWay === 'onSelect' && rowSelection.onSelect) {
      rowSelection.onSelect(record, checked, selectedRows, nativeEvent);
    } else if (selectWay === 'onSelectMultiple' && rowSelection.onSelectMultiple) {
      const changeRows = data.filter(
        (row, i) => changeRowKeys.indexOf(this.getRecordKey(row, i)) >= 0,
      );
      rowSelection.onSelectMultiple(checked, selectedRows, changeRows);
    } else if (selectWay === 'onSelectAll' && rowSelection.onSelectAll) {
      const changeRows = data.filter(
        (row, i) => changeRowKeys.indexOf(this.getRecordKey(row, i)) >= 0,
      );
      rowSelection.onSelectAll(checked, selectedRows, changeRows);
    } else if (selectWay === 'onSelectInvert' && rowSelection.onSelectInvert) {
      rowSelection.onSelectInvert(selectedRowKeys);
    }
  }
  getChildContext() {
    return {
      grid: this.props.grid,
      itemLayout: this.props.itemLayout,
    };
  }
  triggerPaginationEvent(eventName) {
    return (page, pageSize) => {
      const {
        pagination
      } = this.props;
      this.setState({
        paginationCurrent: page,
        paginationSize: pageSize,
      });
      if (pagination && pagination[eventName]) {
        pagination[eventName](page, pageSize);
      }
    };
  }
  renderItem = (item, index) => {
    const {
      renderItem,
      rowKey
    } = this.props;
    if (!renderItem) return null;

    let key;

    if (typeof rowKey === 'function') {
      key = rowKey(item);
    } else if (typeof rowKey === 'string') {
      key = item[rowKey];
    } else {
      key = item.key;
    }

    if (!key) {
      key = `list-item-${index}`;
    }

    this.keys[index] = key;

    return renderItem(item, index);
  };

  isSomethingAfterLastItem() {
    const {
      loadMore,
      pagination,
      footer
    } = this.props;
    return !!(loadMore || pagination || footer);
  }

  renderEmpty = (prefixCls) => {
    const { emptyText } = this.props;

    return (
      <div className={`${prefixCls}-placeholder`}>
        {emptyText}
      </div>
    );
  };
  handleSelect = (record, index, e) => {
    const nativeEvent = e.nativeEvent;
    const defaultSelection = this.store.getState().selectionDirty ? [] : this.getDefaultSelection();
    let selectedRowKeys = this.store.getState().selectedRowKeys.concat(defaultSelection);
    const key = this.getRecordKey(record, index);

    const checked = selectedRowKeys.indexOf(key) === -1;
    const {
      pivot
    } = this.state;
    const rows = this.getFlatCurrentPageData();
    let realIndex = index;
    if (this.props.expandedRowRender) {
      realIndex = rows.findIndex(row => this.getRecordKey(row, index) === key);
    }

    if (nativeEvent.shiftKey && pivot !== undefined && realIndex !== pivot) {
      const changeRowKeys = [];
      const direction = Math.sign(pivot - realIndex);
      const dist = Math.abs(pivot - realIndex);
      let step = 0;
      while (step <= dist) {
        const i = realIndex + step * direction;
        step += 1;
        const row = rows[i];
        const rowKey = this.getRecordKey(row, i);
        const checkboxProps = this.getCheckboxPropsByItem(row, i);
        if (!checkboxProps.disabled) {
          if (selectedRowKeys.includes(rowKey)) {
            if (!checked) {
              selectedRowKeys = selectedRowKeys.filter((j) => rowKey !== j);
              changeRowKeys.push(rowKey);
            }
          } else if (checked) {
            selectedRowKeys.push(rowKey);
            changeRowKeys.push(rowKey);
          }
        }
      }

      this.setState({
        pivot: realIndex
      });
      this.store.setState({
        selectionDirty: true,
      });
      this.setSelectedRowKeys(selectedRowKeys, {
        selectWay: 'onSelectMultiple',
        record,
        checked,
        changeRowKeys,
        nativeEvent,
      });
    } else {
      if (checked) {
        selectedRowKeys.push(this.getRecordKey(record, realIndex));
      } else {
        selectedRowKeys = selectedRowKeys.filter((i) => key !== i);
      }

      this.setState({
        pivot: realIndex
      });
      this.store.setState({
        selectionDirty: true,
      });
      this.setSelectedRowKeys(selectedRowKeys, {
        selectWay: 'onSelect',
        record,
        checked,
        changeRowKeys: void 0,
        nativeEvent,
      });
    }
  };
  handleRadioSelect = (record, index, e) => {
    const nativeEvent = e.nativeEvent;
    const key = this.getRecordKey(record, index);
    const selectedRowKeys = [key];
    this.store.setState({
      selectionDirty: true,
    });
    this.setSelectedRowKeys(selectedRowKeys, {
      selectWay: 'onSelect',
      record,
      checked: true,
      changeRowKeys: void 0,
      nativeEvent,
    });
  };

  renderList = () => {
    const {
      paginationCurrent,
      paginationSize
    } = this.state;
    const {
      prefixCls,
      bordered,
      split,
      className,
      children,
      itemLayout,
      loadMore,
      pagination,
      grid,
      data = [],
      size,
      rowKey,
      renderItem,
      header,
      footer,
      loading,
      emptyText,
      rowSelection,
      ...rest
    } = this.props;

    let loadingProp = loading;
    if (typeof loadingProp === 'boolean') {
      loadingProp = {
        spinning: loadingProp,
      };
    }
    const isLoading = loadingProp && loadingProp.spinning;

    // large => lg
    // small => sm
    let sizeCls = '';
    switch (size) {
      case 'large':
        sizeCls = 'lg';
        break;
      case 'small':
        sizeCls = 'sm';
        break;
      default:
        break;
    }

    const classString = classNames(prefixCls, className, {
      [`${prefixCls}-vertical`]: itemLayout === 'vertical',
      [`${prefixCls}-${sizeCls}`]: sizeCls,
      [`${prefixCls}-split`]: split,
      [`${prefixCls}-bordered`]: bordered,
      [`${prefixCls}-loading`]: isLoading,
      [`${prefixCls}-grid`]: grid,
      [`${prefixCls}-something-after-last-item`]: this.isSomethingAfterLastItem(),
    });

    const paginationProps = {
      ...this.defaultPaginationProps,
      total: data.length,
      ...(pagination || {}),
      pageNumber: paginationCurrent || 1,
      pageSize: paginationSize || 10,
    };

    const total = paginationProps.total;

    const paginationContent = pagination ? (
      <div className={`${prefixCls}-pagination`}>
        <Pagination
          {...paginationProps}
          onChange={this.onPaginationChange}
          onShowSizeChange={this.onPaginationShowSizeChange}
          total={total}
          pageNumber={this.getMaxCurrent(total)}
        />
      </div>
    ) : null;

    let splitData = this.getCurrentPageData()

    let childrenContent;
    childrenContent = isLoading && <div style={{ minHeight: 53 }} />;
    if (splitData.length > 0) {
      const items = splitData.map((item, index) => this.renderItem(item, index));

      const childrenList = [];
      React.Children.forEach(items, (child, index) => {
        const itemData = splitData[index]
        childrenList.push(
          React.cloneElement(child, {
            key: this.keys[index],
            rowKey: this.keys[index],
            prefixCls,
            store: this.store,
            onClick: event => {
              if (rowSelection && !this.getCheckboxPropsByItem(itemData, index).disabled) {
                rowSelection.type === 'radio' ?
                  this.handleRadioSelect(itemData, index, event) :
                  this.handleSelect(itemData, index, event);
              }
            }
          }),
        );
      });

      childrenContent = grid ? (
        <Row gutter={grid.gutter}>{childrenList}</Row>
      ) : (
        <ul className={`${prefixCls}-items`}>{childrenList}</ul>
      );
    } else if (!children && !isLoading) {
      childrenContent = this.renderEmpty(prefixCls);
    }

    return (
      <div className={classString} {...rest}>
        {header && <div className={`${prefixCls}-header`}>{header}</div>}
        <Spin {...loadingProp}>
          {childrenContent}
          {children}
        </Spin>
        {footer && <div className={`${prefixCls}-footer`}>{footer}</div>}
        {loadMore || paginationContent}
      </div>
    );
  };
  render() {
    return <React.Fragment>{this.renderList()}</React.Fragment>;
  }
}